#include"Protocol.hpp"

//1.读取并分析请求
//读取http的请求行
bool EndPoint::RecvHttpRequestLine()
{
    auto &line = http_request.request_line;
    if(Util::ReadLine(sock, line) > 0){
        line.resize(line.size()-1);
        LOG(INFO, http_request.request_line);
    }
    else
    {
        stop = true;
    }
    std::cout << "RecvHttpRequestLine: " << stop << std::endl;
    return stop;
}

//读取http的请求报头
bool EndPoint::RecvHttpRequestHeader()
{
    std::string line;
    while(true)
    {
        line.clear();
        if(Util::ReadLine(sock, line) <= 0){
            stop = true;
            break;
        }
        if(line == "\n")
        {
            http_request.blank = line;
            break;
        }
        line.resize(line.size()-1);
        http_request.request_header.push_back(line);//放入请求报头中
        LOG(INFO, line);
    }
    std::cout <<"stop debug: " << stop << std::endl;
    return stop;
}

//解析请求行
void EndPoint::ParseHttpRequestLine()
{
    auto &line = http_request.request_line;
    std::stringstream ss(line);
    ss >> http_request.method >> http_request.uri >> http_request.version;
    auto &method = http_request.method;
    std::transform(method.begin(), method.end(), method.begin(), ::toupper);
}

//解析请求报头
void EndPoint::ParseHttpRequestHeader()
{
    std::string key;
    std::string value;
    for(auto &iter : http_request.request_header)
    {
        if(Util::CutString(iter, key, value, SEP))
        {
            http_request.header_kv.insert({key, value});//建立k,v的映射关系
        }
    }
}

//判断是否读取请求正文(POST有正文, GET无)
bool EndPoint::IsNeedRecvHttpRequestBody()
{
    auto &method = http_request.method;
    if(method == "POST")
    {
        auto &header_kv = http_request.header_kv;
        auto iter = header_kv.find("Content-Length");
        if(iter != header_kv.end())
        {
            LOG(INFO, "Post Method, Content-Length: "+ iter->second);
            http_request.content_length = atoi(iter->second.c_str());
            return true;
        }
    }
    return false;
}

//读取请求正文
bool EndPoint::RecvHttpRequestBody()
{
    if(IsNeedRecvHttpRequestBody()){
        int content_length = http_request.content_length;
        auto &body = http_request.request_body;

        char ch = 0;
        while(content_length)
        {
            ssize_t s = recv(sock, &ch, 1, 0);
            if(s > 0)
            {
                body.push_back(ch);
                content_length--;
            }
            else
            {
                stop = true;
                break;
            }
        }
        LOG(INFO, body);
    }
    return stop;
}

//***************************************************************************************************************
//2.设计CGI
int EndPoint::ProcessCgi()
{
    LOG(INFO, "process cgi mthod!");

    int code = OK;
    //父进程数据
    auto &method = http_request.method;
    auto &query_string =  http_request.query_string;        //GET的参数
    auto &body_text = http_request.request_body;            //POST的正文数据
    auto &bin = http_request.path;                          //要执行的程序的路径
    int content_length = http_request.content_length;      
    auto &response_body = http_response.response_body;      //通过cgi要拿到的响应正文

    std::string query_string_env;
    std::string method_env;
    std::string content_length_env;

    //创建管道, 与cgi程序进行通信
    int input[2]; int output[2];
    if(pipe(input) < 0)
    {
        LOG(ERROR, "pipe input error");
        code = SERVER_ERROR;
        return code;
    }
    if(pipe(output) < 0)
    {
        LOG(ERROR, "pipe output error");
        code = SERVER_ERROR;
        return code;
    }

    //创建子进程, 进行程序替换, 来处理请求
    pid_t pid = fork();
    if(pid == 0 )
    {   //child
        close(input[0]); close(output[1]);
        
        //导入环境变量->让子进程知道是那种请求方法
        method_env = "METHOD=";
        method_env += method;
        putenv((char*)method_env.c_str());

        if(method == "GET")
        {
            query_string_env = "QUERY_STRING=";
            query_string_env += query_string;
            putenv((char*)query_string_env.c_str());
            LOG(INFO, "Get Method, Add Query_String Env");
        }
        else if(method == "POST")
        {
            content_length_env = "CONTENT_LENGTH=";
            content_length_env += std::to_string(content_length);
            putenv((char*)content_length_env.c_str());
            LOG(INFO, "Post Method, Add Content_Length Env");
        }

        //使用重定向, 让替换成功之后的子进程无需知道对应的读写文件描述符是多少, 只要读0， 写1即可
        std::cout << "bin: " << bin << std::endl;
        dup2(output[0], 0); dup2(input[1], 1);
        execl(bin.c_str(), bin.c_str(), nullptr);//执行cgi程序
        exit(1);
    }
    else if(pid < 0)
    {   //error
        LOG(ERROR, "fork error!");
        return 404;
    }
    else
    {   //parent
        close(input[1]); close(output[0]);
        
        //向管道里写数据(请求正文), 让cgi程序能获取数据 + 处理数据
        if(method == "POST")
        {
            const char *start = body_text.c_str();
            int total = 0;
            int size = 0;
            while(total < content_length && (size = write(output[1], start+total, body_text.size()-total)) > 0)
            {
                total += size;
            }
        }
        //cgi程序处理完, 父进程读取结果, 用于拿到response_body来构建响应返回给客户端
        char ch = 0;
        while(read(input[0], &ch, 1) > 0)
        {
            response_body.push_back(ch);
        }
        int status = 0;
        pid_t ret = waitpid(pid, &status, 0);
        if(ret == pid)
        {
            if(WIFEXITED(status))
            {
                if(WEXITSTATUS(status) == 0)
                    code = OK;
                else
                    code = BAD_REQUEST;
            }
            else
            {
                code = SERVER_ERROR;
            }
        }

        close(input[0]);
        close(output[1]);
    }
    return code;
}

//非CGI处理: 打开该文件, 并记录fd. 后面直接使用sendfile将其发送给客户端
int EndPoint::ProcessNonCgi()
{
    http_response.fd = open(http_request.path.c_str(), O_RDONLY);
    if(http_response.fd >= 0)
    {
        LOG(INFO, http_request.path + " open success!");
        return OK;
    }
    return NOT_FOUND;
}


//错误处理
//返回404页面,其构建的报文(响应报头)
void EndPoint::HandlerError(std::string page)
{
    std::cout << "debug: " << page << std::endl;
    http_request.cgi = false;
    //要给用户返回对应的404页面
    http_response.fd = open(page.c_str(), O_RDONLY);
    if(http_response.fd > 0){
        struct stat st;
        stat(page.c_str(), &st);
        http_request.size = st.st_size;

        std::string line = "Content-Type: text/html";
        line += LINE_END;
        http_response.response_header.push_back(line);

        line = "Content-Length: ";
        line += std::to_string(st.st_size);
        line += LINE_END;
        http_response.response_header.push_back(line);
    }
}


//4.构建响应与发送响应
//响应报头
void EndPoint::BuildOkResponse()
{   
    
    std::string line = "Content-Type: ";
    line += Suffix2Desc(http_request.suffix);
    line += LINE_END;
    http_response.response_header.push_back(line);

    line = "Content-Length: ";
    if(http_request.cgi)
    {
        line += std::to_string(http_response.response_body.size()); //响应正文的大小
    }
    else
    {
        line += std::to_string(http_request.size);                  //对应的资源大小
    }
    line += LINE_END;
    http_response.response_header.push_back(line);
}

void EndPoint::BuildHttpResponseHelper()
{
    auto &code = http_response.status_code;
    //1.构建状态行: HTTP版本号、状态码和状态消息。
    auto &status_line = http_response.status_line;
    status_line += HTTP_VERSION;
    status_line += " ";
    status_line += std::to_string(code);
    status_line += " ";
    status_line += Code2Desc(code);
    status_line += LINE_END;

    //2.构建响应报头
    std::string path = WEB_ROOT;
    path += "/";
    switch(code){
        case OK:
            BuildOkResponse();
            break;
        case NOT_FOUND:
            path += PAGE_404;
            HandlerError(path);
            break;
        case BAD_REQUEST:
            path += PAGE_400;
            HandlerError(path);
            break;
        case SERVER_ERROR:
            path += PAGE_500;
            HandlerError(path);
            break;
        default:
            break;
    }
}